﻿using System;
using System.Data;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using PublicDomain.Data.Csv;
using PublicDomain.Framework.UnitTesting;
using PublicDomain.Framework.UnitTesting.DataDrivenTest;

namespace PublicDomain.UnitTest.Data.Csv
{
    [TestClass]
    public class CsvCommandTest
    {
        public TestContext TestContext { get; set; }

        public void TestInitialize()
        {
        }

        [TestMethod]
        [Description("Tests constructor CsvCommand(string commandText, CsvConnection csvConnection).")]
        public void CsvCommandConstructor3Test()
        {
            var expectedCommandText = "TestData.csv";
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand(expectedCommandText, expectedCsvConnection);

            Assert.AreEqual(expectedCommandText, target.CommandText);
            Assert.AreSame(expectedCsvConnection, target.Connection);
        }

        [TestMethod]
        [Description("Tests constructor CsvCommand(string commandText).")]
        public void CsvCommandConstructor2Test()
        {
            var expectedCommandText = "TestData.csv";

            var target = new CsvCommand(expectedCommandText);

            Assert.AreEqual(expectedCommandText, target.CommandText);
            Assert.IsNull(target.Connection);
        }

        [TestMethod]
        [Description("Tests constructor CsvCommand().")]
        public void CsvCommandConstructorTest()
        {
            var target = new CsvCommand();

            Assert.IsNull(target.CommandText);
            Assert.IsNull(target.Connection);
        }

        [TestMethod]
        [Description("Tests that CsvCommand.CommandType is always CommandType.TableDirect.")]
        public void CommandTypeSetTest()
        {
            AssertExtensions.Throws<CsvCommandException>(
                () =>
                {
                    var target = new CsvCommand();

                    Assert.AreEqual(CommandType.TableDirect, target.CommandType);

                    target.CommandType = CommandType.Text;
                },
                "The command type of the CsvCommand is always CommandType.TableDirect and cannot be changed.");
        }

        [TestMethod]
        [Description("Tests property CsvCommand.CommandText on get and set.")]
        public void CommandTextTest()
        {
            var expectedCommandText = "TestData.csv";

            var target = new CsvCommand();

            target.CommandText = expectedCommandText;

            Assert.AreEqual(expectedCommandText, target.CommandText);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.Connection on get and set .")]
        public void ConnectionTest()
        {
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand();

            target.Connection = expectedCsvConnection;

            Assert.AreSame(expectedCsvConnection, target.Connection);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.CommandTimeout on get.")]
        public void CommandTimeoutGetTest()
        {
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand();

            Assert.AreEqual(-1, target.CommandTimeout);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.CommandTimeout on set.")]
        public void CommandTimeoutSetTest()
        {
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand();

            target.CommandTimeout = int.MaxValue;

            Assert.AreEqual(-1, target.CommandTimeout);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.Parameters on get.")]
        public void ParametersGetTest()
        {
            var target = new CsvCommand();

            Assert.IsNull(target.Parameters);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.Transaction on get.")]
        public void TransactionGetTest()
        {
            var target = new CsvCommand();

            Assert.IsNull(target.Transaction);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.Transaction on set.")]
        public void TransactionSetTest()
        {
            var target = new CsvCommand();

            target.Transaction = null;

            Assert.IsNull(target.Transaction);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.UpdatedRowSource on get.")]
        public void UpdatedRowSourceGetTest()
        {
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand();

            Assert.AreEqual(UpdateRowSource.None, target.UpdatedRowSource);
        }

        [TestMethod]
        [Description("Tests property CsvCommand.UpdatedRowSource on set.")]
        public void UpdatedRowSourceSetTest()
        {
            var expectedCsvConnection = new CsvConnection();

            var target = new CsvCommand();

            target.UpdatedRowSource = UpdateRowSource.None;
            Assert.AreEqual(UpdateRowSource.None, target.UpdatedRowSource);
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ExecuteReader(CommandBehavior behavior).")]
        [ExpectedException(typeof(NotImplementedException))]
        public void ExecuteReaderCommandBehaviorTest()
        {
            var target = new CsvCommand();

            target.ExecuteReader(CommandBehavior.Default);
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ExecuteReader.")]
        [DeploymentItem("CsvDefaultFile.csv")]
        public void ExecuteReaderTest()
        {
            var target = new CsvCommand("CsvDefaultFile.csv");

            var dataReader = target.ExecuteReader();

            Assert.IsInstanceOfType(dataReader, typeof(CsvDataReader));
        }

        [TestMethod]
        [Description("Tests method CsvCommand.Dispose.")]
        public void DisposeTest()
        {
            var target = new CsvCommand();

            target.Dispose();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.Prepare.")]
        [ExpectedException(typeof(NotImplementedException))]
        public void PrepareTest()
        {
            var target = new CsvCommand();

            target.Prepare();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.Cancel.")]
        [ExpectedException(typeof(NotImplementedException))]
        public void CancelTest()
        {
            var target = new CsvCommand();

            target.Cancel();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.CreateParameter.")]
        [ExpectedException(typeof(NotImplementedException))]
        public void Test()
        {
            var target = new CsvCommand();

            target.CreateParameter();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ExecuteNonQuery.")]
        [ExpectedException(typeof(NotImplementedException))]
        public void ExecuteNonQueryTest()
        {
            var target = new CsvCommand();

            target.ExecuteNonQuery();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ExecuteScalar.")]
        [ExpectedException(typeof(NotImplementedException))]
        public void ExecuteScalarTest()
        {
            var target = new CsvCommand();

            target.ExecuteScalar();
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ResolveCsvFilePath for the case of empty Connection and CommandText.")]
        public void ResolveCsvFilePathEmptyConnectionAndCommandTextTest()
        {
            var target = new CsvCommand_Accessor();

            var actual = target.ResolveCsvFilePath();

            Assert.IsNull(actual);
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ResolveCsvFilePath for the case of initialized Connection and CommandText.")]
        public void ResolveCsvFilePathInitializedConnectionAndCommandTextTest()
        {
            var target = new CsvCommand_Accessor("CsvFileName.csv", new CsvConnection("Database=C:\\Temp\\"));

            var actual = target.ResolveCsvFilePath();

            Assert.AreEqual("C:\\Temp\\CsvFileName.csv", actual);
        }

        [TestMethod]
        [Description("Tests method CsvCommand.ResolveCsvFilePath for the case of initialized Connection and CommandText as full file path.")]
        public void ResolveCsvFilePathInitializedConnectionAndFullFilePathCommandTextTest()
        {
            var target = new CsvCommand_Accessor("C:\\Temp2\\CsvFileName.csv", new CsvConnection("Database=C:\\Temp\\"));

            var actual = target.ResolveCsvFilePath();

            Assert.AreEqual("C:\\Temp2\\CsvFileName.csv", actual);
        }

        [TestMethod]
        [Description("CsvCommand end to end test using default constructor and connection property.")]
        [DeploymentItem("ExecuteReaderTestInput.csv")]
        [DeploymentItem("ExecuteReaderTestInputSchemata.xml")]
        [DeploymentItem("ExecuteReaderTestInputPipeDelimiter.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount2.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount0.csv")]
        [DeploymentItem("ExecuteReaderTestInputLessColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputMoreColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputEscapedEmptyAsZeroLengthString.csv")]
        [DeploymentItem("ExecuteReaderTestInputTabDelimiter.csv")]
        [DeploymentItem("ExecuteReaderTestInputAnsiEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUtf8Encoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeBigEndianEncoding.csv")]
        [DeploymentItem("CsvDataReaderTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvCommandTestData.mdb", "CsvCommandTestData", DataAccessMethod.Sequential)]
        public void CsvCommandDefaultConstructorExecuteReaderTest()
        {
            var dataDrivenTest = new DataDrivenTest<CsvCommandTestParams, CsvCommandTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;

                var target = new CsvCommand();
                target.CommandText = testParams.CommandText;

                if (!testParams.UseDefaultConnection)
                {
                    var connection = new CsvConnection(BuildConnectionString(testParams));
                    target.Connection = connection;
                }

                this.CsvCommandExecuteReaderTestAndAssert(dataDrivenTest, target);
            });
        }

        [TestMethod]
        [Description("CsvCommand end to end test using constructor with command text and connection property.")]
        [DeploymentItem("ExecuteReaderTestInput.csv")]
        [DeploymentItem("ExecuteReaderTestInputSchemata.xml")]
        [DeploymentItem("ExecuteReaderTestInputPipeDelimiter.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount2.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount0.csv")]
        [DeploymentItem("ExecuteReaderTestInputLessColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputMoreColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputEscapedEmptyAsZeroLengthString.csv")]
        [DeploymentItem("ExecuteReaderTestInputTabDelimiter.csv")]
        [DeploymentItem("ExecuteReaderTestInputAnsiEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUtf8Encoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeBigEndianEncoding.csv")]
        [DeploymentItem("CsvDataReaderTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvCommandTestData.mdb", "CsvCommandTestData", DataAccessMethod.Sequential)]
        public void CsvCommandConstructorCommandTextExecuteReaderTest()
        {
            var dataDrivenTest = new DataDrivenTest<CsvCommandTestParams, CsvCommandTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;

                var target = new CsvCommand(testParams.CommandText);

                if (!testParams.UseDefaultConnection)
                {
                    var connection = new CsvConnection(BuildConnectionString(testParams));
                    target.Connection = connection;
                }

                this.CsvCommandExecuteReaderTestAndAssert(dataDrivenTest, target);
            });
        }

        [TestMethod]
        [Description("CsvCommand end to end test using constructor with command text and connection.")]
        [DeploymentItem("ExecuteReaderTestInputSchemata.xml")]
        [DeploymentItem("ExecuteReaderTestInput.csv")]
        [DeploymentItem("ExecuteReaderTestInputPipeDelimiter.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount2.csv")]
        [DeploymentItem("ExecuteReaderTestInputHeaderRowsCount0.csv")]
        [DeploymentItem("ExecuteReaderTestInputLessColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputMoreColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputEscapedEmptyAsZeroLengthString.csv")]
        [DeploymentItem("ExecuteReaderTestInputTabDelimiter.csv")]
        [DeploymentItem("ExecuteReaderLessColumnsTestInput.csv")]
        [DeploymentItem("ExecuteReaderTestInputAnsiEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUtf8Encoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeEncoding.csv")]
        [DeploymentItem("ExecuteReaderTestInputUnicodeBigEndianEncoding.csv")]
        [DeploymentItem("CsvDataReaderTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvCommandTestData.mdb", "CsvCommandTestData", DataAccessMethod.Sequential)]
        public void CsvCommandConstructorCommandTextConnectionExecuteReaderTest()
        {
            var dataDrivenTest = new DataDrivenTest<CsvCommandTestParams, CsvCommandTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;

                CsvConnection connection;
                if (testParams.UseDefaultConnection)
                {
                    connection = new CsvConnection();
                }
                else
                {
                    connection = new CsvConnection(BuildConnectionString(testParams));
                }

                var target = new CsvCommand(testParams.CommandText, connection);

                this.CsvCommandExecuteReaderTestAndAssert(dataDrivenTest, target);
            });
        }

        [TestMethod]
        [Description("Tests CsvCommand.ExecuteReader on CsvParsingOptions error handling.")]
        [DeploymentItem("ExecuteReaderTestInputSchemata.xml")]
        [DeploymentItem("ExecuteReaderTestInputLessColumns.csv")]
        [DeploymentItem("ExecuteReaderTestInputMoreColumns.csv")]
        [DeploymentItem("CsvDataReaderTestData.mdb")]
        [DataSource("System.Data.OleDb", "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\\CsvCommandTestData.mdb", "CsvCommandErrorHandlingTestData", DataAccessMethod.Sequential)]
        public void CsvCommandExecuteReaderCsvParsingOptionsErrorHandlingTest()
        {
            var dataDrivenTest = new DataDrivenTest<CsvCommandErrorHandlingTestParams, CsvCommandErrorHandlingTestExpectedResults>(this.TestContext);

            dataDrivenTest.ExecuteTest(() =>
            {
                var testParams = dataDrivenTest.TestParams;
                var expectedResults = dataDrivenTest.ExpectedResults;

                var connection = new CsvConnection(BuildConnectionString(testParams));

                var target = new CsvCommand(testParams.CommandText, connection);

                AssertExtensions.Throws(
                    expectedResults.ExceptionType,
                    () =>
                    {
                        using (var dataReader = target.ExecuteReader())
                        {
                            dataReader.Read();
                        }
                    },
                    expectedResults.ErrorMessage);
            });
        }

        private void CsvCommandExecuteReaderTestAndAssert(DataDrivenTest<CsvCommandTestParams, CsvCommandTestExpectedResults> dataDrivenTest, CsvCommand target)
        {
            var testParams = dataDrivenTest.TestParams;
            var expectedResults = dataDrivenTest.ExpectedResults;

            using (var dataReader = target.ExecuteReader())
            {
                var csvDataReader = dataReader as CsvDataReader;

                Assert.IsInstanceOfType(dataReader, typeof(CsvDataReader));
                Assert.IsNotNull(csvDataReader);

                var readResult = dataReader.Read();

                Assert.IsTrue(readResult);
                Assert.AreEqual(expectedResults.ColumnsParsed, csvDataReader.ColumnsParsed);

                var csvColumnsDefinitions = csvDataReader.CsvDataFormat.CsvColumnsDefinitions;
                Assert.AreEqual(2, csvColumnsDefinitions.Count);
                Assert.AreEqual(expectedResults.Column1DbType, csvColumnsDefinitions[0].DataType);
                Assert.AreEqual(expectedResults.Column2DbType, csvColumnsDefinitions[1].DataType);

                var column1Value = dataReader[0];
                Assert.AreEqual(expectedResults.Column1ValueText, dataReader.GetString(0));
                Assert.AreEqual(expectedResults.Column1ValueText, dataReader.GetString(dataReader.GetOrdinal(csvColumnsDefinitions[0].ColumnName)));
                Assert.IsInstanceOfType(column1Value, expectedResults.Column1ValueType);

                var column2Value = dataReader[1];
                Assert.AreEqual(expectedResults.Column2ValueText, dataReader.GetString(1));
                Assert.AreEqual(expectedResults.Column2ValueText, dataReader.GetString(dataReader.GetOrdinal(csvColumnsDefinitions[1].ColumnName)));
                Assert.IsInstanceOfType(column2Value, expectedResults.Column2ValueType);
            }
        }

        private string BuildConnectionString(CsvCommandTestParams testParams)
        {
            var connectionTextItems = new string[5];
            if (testParams.ConnectionUseDatabase.HasValue && testParams.ConnectionUseDatabase.Value == true)
            {
                connectionTextItems[0] = String.Format("Database={0}", this.TestContext.TestDeploymentDir);
            }

            if (!String.IsNullOrWhiteSpace(testParams.ConnectionSchemataFileName))
            {
                var schemataFilePath = Path.Combine(this.TestContext.TestDeploymentDir, testParams.ConnectionSchemataFileName);
                connectionTextItems[1] = String.Format("SchemataFilePath={0}", schemataFilePath);
            }

            if (testParams.ConnectionDelimiter.HasValue)
            {
                connectionTextItems[2] = String.Format("Delimiter={0}", Regex.Escape(new string(new char[] { testParams.ConnectionDelimiter.Value })));
            }

            if (testParams.ConnectionHeaderRowsCount.HasValue)
            {
                connectionTextItems[3] = String.Format("HeaderRowsCount={0}", testParams.ConnectionHeaderRowsCount.Value);
            }

            if (testParams.ConnectionParsingOptions.HasValue)
            {
                connectionTextItems[4] = String.Format("ParsingOptions={0}", testParams.ConnectionParsingOptions.Value);
            }

            var connectionTextBuilder = new StringBuilder();
            foreach (var connectionTextItem in connectionTextItems)
            {
                if (!String.IsNullOrWhiteSpace(connectionTextItem))
                {
                    if (connectionTextBuilder.Length != 0)
                    {
                        connectionTextBuilder.Append(';');
                    }

                    connectionTextBuilder.Append(connectionTextItem);
                }
            }

            var result = connectionTextBuilder.ToString();

            return result;
        }

        private class CsvCommandTestParams
        {
            [DataTableColumnName("TestParamCommandText")]
            public string CommandText { get; set; }

            [DataTableColumnName("TestParamUseDefaultConnection")]
            public bool UseDefaultConnection { get; set; }

            [DataTableColumnName("TestParamConnectionUseDatabase")]
            public bool? ConnectionUseDatabase { get; set; }

            [DataTableColumnName("TestParamConnectionSchemataFileName")]
            public string ConnectionSchemataFileName { get; set; }

            [DataTableColumnName("TestParamConnectionDelimiter")]
            public char? ConnectionDelimiter { get; set; }

            [DataTableColumnName("TestParamConnectionHeaderRowsCount")]
            public int? ConnectionHeaderRowsCount { get; set; }

            [DataTableColumnName("TestParamConnectionParsingOptions")]
            public CsvParsingOptions? ConnectionParsingOptions { get; set; }
        }

        private class CsvCommandTestExpectedResults
        {
            [DataTableColumnName("ExpectedResultColumn1DbType")]
            public DbType Column1DbType { get; set; }

            [DataTableColumnName("ExpectedResultColumn1ValueType")]
            public Type Column1ValueType { get; set; }

            [DataTableColumnName("ExpectedResultColumn1ValueText")]
            public string Column1ValueText { get; set; }

            [DataTableColumnName("ExpectedResultColumn2DbType")]
            public DbType Column2DbType { get; set; }

            [DataTableColumnName("ExpectedResultColumn2ValueType")]
            public Type Column2ValueType { get; set; }

            [DataTableColumnName("ExpectedResultColumn2ValueText")]
            public string Column2ValueText { get; set; }

            [DataTableColumnName("ExpectedResultColumnsParsed")]
            public int ColumnsParsed { get; set; }
        }

        private class CsvCommandErrorHandlingTestParams : CsvCommandTestParams
        {
        }

        private class CsvCommandErrorHandlingTestExpectedResults
        {
            [DataTableColumnName("ExpectedResultExceptionType")]
            public Type ExceptionType { get; set; }

            [DataTableColumnName("ExpectedResultErrorMessage")]
            public string ErrorMessage { get; set; }
        }
    }
}
